/**********************
**intellistrip.inc
**Date Created: 4/13/2009
**Copyright 2009 Tomorrow Tech Industries
**Contact: RPITomorrowTech@gmail.com
**********************/
/*
This program is based off a program with the line:
Copyright(C) Mark Zehner/Peter Baumann 1999
*/

//Haven't suppported other compilers yet
#ifndef __GNUC__
#error Only GCC/G++ supported at this time
#endif

#ifdef WIN_32
#error Windows target not supported at this time
#endif

#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <string>
#include <stdarg.h>
#include "intellistrip.h"
#include "intellistrip_serial.h"

using namespace std;

int g_verbose = TRUE;

//POSIX compliant source
#define _POSIX_SOURCE 1			

string IntellistripSerial::lastError = "";

//TRUE while no signal received
//Unfortunatly, cannot currently distinguish between callbacks, so this must be a static wakeup
int IntellistripSerial::waitFlag = FALSE;


IntellistripSerial::IntellistripSerial()
{
	//Make sure user doesn't try to do anything before init
	active = FALSE;
	waitFlag = TRUE;
}

IntellistripSerial::~IntellistripSerial()
{
	// restore old port settings
	tcsetattr(serialFd, TCSANOW, &oldtio);
	//close the com port
	close(serialFd);		  
}

ttRC IntellistripSerial::init(string serialDevice, long baudRate, int dataBits, int stopBits, int parity, string password)
{
	//Return code
	ttRC rc = TT_GENERAL;
	//Signal handling, especially for knowing when we have new data
	struct sigaction saio;
	//These numbers are not used directly, they are converted to defines and require common values
	long badDerrived;
	long dataBitsDerrived;
	long stopBitsDerrived;
	long parityOnDerrived;
	long parityDerrived;

	this->serialDevice = serialDevice;
	this->baudRate = baudRate;
	this->dataBits = dataBits;
	this->stopBits = stopBits;
	this->parity = parity;
	this->password = password;

	printf_debug("Device=%s, Baud=%li\n", serialDevice.c_str(), baudRate);
	printf_debug("Data Bits=%i  Stop Bits=%i  parity=%i\n", dataBits, stopBits, parity);

	switch( baudRate )
	{
	case 38400:
	default:
		badDerrived = B38400;
		break;
	case 19200:
		badDerrived  = B19200;
		break;
	case 9600:
		badDerrived  = B9600;
		break;
	case 4800:
		badDerrived  = B4800;
		break;
	case 2400:
		badDerrived  = B2400;
		break;
	case 1800:
		badDerrived  = B1800;
		break;
	case 1200:
		badDerrived  = B1200;
		break;
	case 600:
		badDerrived  = B600;
		break;
	case 300:
		badDerrived  = B300;
		break;
	case 200:
		badDerrived  = B200;
		break;
	case 150:
		badDerrived  = B150;
		break;
	case 134:
		badDerrived  = B134;
		break;
	case 110:
		badDerrived  = B110;
		break;
	case 75:
		badDerrived  = B75;
		break;
	case 50:
		badDerrived  = B50;
		break;
	}
	
	switch(dataBits )
	{
	case 8:
	default:
		dataBitsDerrived = CS8;
		break;
	case 7:
		dataBitsDerrived = CS7;
		break;
	case 6:
		dataBitsDerrived = CS6;
		break;
	case 5:
		dataBitsDerrived = CS5;
		break;
	}
	
	switch(stopBits )
	{
	case 1:
	default:
		stopBitsDerrived = 0;
		break;
	case 2:
		stopBitsDerrived = CSTOPB;
		break;
	}

	switch(parity)
	{
	//none
	case 0:
	default:							  
		parityOnDerrived = 0;
		parityDerrived = 0;
		break;
	//odd
	case 1:								
		parityOnDerrived = PARENB;
		parityDerrived = PARODD;
		break;
	//even
	case 2:								
		parityOnDerrived = PARENB;
		parityDerrived = 0;
		break;
	}  
	 
	//open the device(com port) to be non-blocking (read will return immediately )
	serialFd = open(serialDevice.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
	if( serialFd < 0 )
	{
		setLastError("Error opening device: %s", serialDevice.c_str());
		printf_debug("%s\n", getLastError().c_str());
		goto error;
	}

	//install the serial handler before making the device asynchronous
	saio.sa_handler = IntellistripSerial::signalHandlerIO;
	sigemptyset(&saio.sa_mask);	//saio.sa_mask = 0;
	saio.sa_flags = 0;
	saio.sa_restorer = NULL;
	sigaction(SIGIO, &saio, NULL);

	// allow the process to receive SIGIO
	fcntl(serialFd, F_SETOWN, getpid());
	// Make the file descriptor asynchronous (the manual page says only
	// O_APPEND and O_NONBLOCK, will work with F_SETFL... )
	fcntl(serialFd, F_SETFL, FASYNC);

	tcgetattr(serialFd, &oldtio); // save current port settings 
	// set new port settings for canonical g_input processing 
	newtio.c_cflag = badDerrived | CRTSCTS | dataBitsDerrived | stopBitsDerrived | parityOnDerrived | parityDerrived | CLOCAL | CREAD;
	newtio.c_iflag = IGNPAR;
	newtio.c_oflag = 0;
	//ICANON;
	newtio.c_lflag = 0;		 
	newtio.c_cc[VMIN] = 1;
	newtio.c_cc[VTIME] = 0;
	tcflush(serialFd, TCIFLUSH);
	tcsetattr(serialFd, TCSANOW, &newtio);
	
	active = TRUE;
	rc = TT_OK;
	
error:
	return rc;
}

ttRC IntellistripSerial::readChar(ttUI8_t *charRead)
{
	return readBuffer(charRead, 1);
}

ttRC IntellistripSerial::readBuffer(ttUI8_t *buffer, int bufferSize)
{
	ttRC rc = TT_GENERAL;
	
	if( !buffer )
	{
		return TT_GENERAL;
	}

	printf_debug("readBuffer(buff, %d)\n", bufferSize);

	while( bufferSize > 0 )
	{
		if( !active )
		{
			setLastError("Not active");
			goto error;
		}

		//if( waitFlag == FALSE )
		{
			int res = 0;
			
			printf_debug("read(fd, buffer, %d)\n", bufferSize);
			res = read(serialFd, buffer, bufferSize);
			printf_debug("read, res: %d\n", res);
	
			if( res < 0 )
			{
				setLastError("Failed to read char");
				goto error;
			}
			else if( res == 0 )
			{
				//Wait / try again
				waitFlag = TRUE;		
				continue;
			}
			printf_debug("Read char: %d\n", buffer[0]);
			//Something was read
			//Advance to next position in buffer
			buffer += res;
			//Decrease number of chars we are expecting
			bufferSize -= res;
		}	
	}

	rc = TT_OK;

error:
	return rc;
}

ttRC IntellistripSerial::writeChar(ttUI8_t toWrite)
{
	return writeBuffer(&toWrite, 1);
}

ttRC IntellistripSerial::writeBuffer(const ttUI8_t *buffer, int bufferSize)
{
	ttRC rc = TT_GENERAL;

	printf_debug("writeBuffer(buffer, %d)\n", bufferSize);

	while( bufferSize > 0 )
	{
		int res = 0;
		if( !active )
		{
			setLastError("Not active");
			goto error;
		}

		res = write(serialFd, buffer, bufferSize);
		res = write(serialFd, buffer, bufferSize);
		if( res < 0 )
		{
			setLastError("Failed to write char");
			goto error;
		}
		else if( res == 0 )
		{
			//Wait / try again
			continue;
		}
		//Something was read
		//Advance to next position in buffer
		buffer += res;
		//Decrease number of chars we are expecting
		bufferSize -= res;
	}
	if( tcdrain(serialFd) )
	{
		printf_debug("Couldn't drain\n");
		goto error;
	}

	rc = TT_OK;

error:
	return rc;
}

ttRC IntellistripSerial::sendRequest(const struct IntellistripSerialRequest *request)
{
	ttRC rc = TT_GENERAL;

	printf_debug("sendRequest()\n");

	if( !active )
	{
		return TT_GENERAL;
	}

	//Size
	if( TT_FAILED(writeChar(request->header.size)) )
	{
		setLastError("Failed to send size");
		goto error;
	}
	
	//Checksum
	if( TT_FAILED(writeChar(request->header.checksum)) )
	{
		setLastError("Failed to send checksum");
		goto error;
	}
	
	//Request
	if( TT_FAILED(writeChar(request->request)) )
	{
		setLastError("Failed to send request");
		goto error;
	}
	
	//Payload
	if( TT_FAILED(writeBuffer(request->additional, request->header.size)) )
	{
		setLastError("Failed to read payload");
		goto error;
	}
	
	rc = TT_OK;

error:
	return rc;
	
}

ttRC IntellistripSerial::recieveResponse(struct IntellistripSerialResponse **responseRet)
{
	ttRC rc = TT_GENERAL;
	struct IntellistripSerialResponse *response = NULL;

	printf_debug("recieveResponse()\n");

	if( !active )
	{
		return TT_GENERAL;
	}
	if( !responseRet )
	{
		return TT_INVALID_INPUT;
	}
	
	//Allocate max size for now, can change later if we are more space concious
	response = (IntellistripSerialResponse *)malloc(MAX_REQUEST__SIZE);
	
	// loop while waiting for input. normally we would do something useful here
	while( active )
	{
		//This waitFlag bussiness looks racy
		//It seemed to indicate a callback was set, but it must have been forgotten to be added to the code or something
		//There is only the other callback, which I see no reason why couldn't execute first
		//We need a mutex at the very least
		//if( waitFlag == FALSE )
		{
			//Size
			if( TT_FAILED(readChar(&response->header.size)) )
			{
				setLastError("Failed to read size");
				goto error;
			}
			
			//Checksum
			if( TT_FAILED(readChar(&response->header.checksum)) )
			{
				setLastError("Failed to read checksum");
				goto error;
			}
			
			//Return code
			if( TT_FAILED(readChar((ttUI8_t *)&response->rc)) )
			{
				setLastError("Failed to read rc");
				goto error;
			}
			
			//Payload
			if( TT_FAILED(readBuffer(response->additional, response->header.size)) )
			{
				setLastError("Failed to read payload");
				goto error;
			}
			
			// wait for new input
			waitFlag = TRUE;		
		} 
	}
	rc = TT_OK;

error:
	if( TT_FAILED(rc) )
	{
		//Error, free our response
		free(response);
	}
	else
	{
		//Save our response
		*responseRet = response;
	}
	return rc;
}

//This is hard coded in main.cpp for now
ttRC IntellistripSerial::getIntellistripInformation(string &vendorName, string &modelNumber, 
								string &modelVersion, string &serialNumber, 
								int &numberPorts, int &state)
{
	return TT_UNSUPPORTED;
}

struct IntellistripSerialRequest *allocateRequest(int payloadSize)
{
	return (struct IntellistripSerialRequest *)malloc(sizeof(struct IntellistripSerialRequest) + payloadSize);
}

ttRC IntellistripSerial::setPortPower(int port, int powerLevel)
{
	ttRC rc = TT_GENERAL;
	struct IntellistripSerialRequest *request = NULL;
	struct IntellistripSerialResponse *response = NULL;
	
	printf_debug("setPortPower(%d, %d)\n", port, powerLevel);
	request = allocateRequest(2);
	if( !request )
	{
		rc = TT_MEM;
		goto error;
	}
	
	request->request = REQUEST__SET_PORT_POWER;
	request->header.size = 2;
	request->additional[REQUEST__SET_PORT_POWER__PORT] = port;
	request->additional[REQUEST__SET_PORT_POWER__LEVEL] = powerLevel;

	port = 1 << (port - 1);;
	writeChar(port);

	/*
	if( TT_FAILED(sendRequest(request)) )
	{
		goto error;
	}
	*/
	
	/*
	if( TT_FAILED(recieveResponse(&response)) )
	{
		goto error;
	}
	*/
	
	//Propogate the MCU's rc
	//rc = response->rc;
	rc = TT_OK;

error:
	if( request )
	{
		free(request);
	}
	if( response )
	{
		free(response);
	}
	return rc;
}

ttRC IntellistripSerial::getPortPower(int port, int &powerLevel)
{
	ttRC rc = TT_GENERAL;
	struct IntellistripSerialRequest *request = NULL;
	struct IntellistripSerialResponse *response = NULL;
	
	request = allocateRequest(1);
	if( !request )
	{
		rc = TT_MEM;
		goto error;
	}
	
	request->request = REQUEST__GET_PORT_POWER;
	request->header.size = 1;
	request->additional[REQUEST__GET_PORT_POWER__PORT] = port;

	if( TT_FAILED(sendRequest(request)) )
	{
		goto error;
	}
	
	/*
	if( TT_FAILED(recieveResponse(&response)) )
	{
		goto error;
	}
	*/
	powerLevel = 0;
	
	powerLevel = response->additional[RESPONSE__GET_PORT_POWER__LEVEL];
	//Propogate the MCU's rc
	rc = response->rc;

error:
	if( request )
	{
		free(request);
	}
	if( response )
	{
		free(response);
	}
	return rc;
}

ttRC IntellistripSerial::setPassword(string newPassword)
{
	return TT_UNSUPPORTED;
}

string IntellistripSerial::getLastError()
{
	return lastError;
}

void IntellistripSerial::clearLastError()
{
	lastError = "";
}

//Variadic formats like in printf()
//I tend to remember something bad about pointers being the va_start arg, but examples seem to show it fine
void IntellistripSerial::setLastError(const char *errorFormat, ...)
{
	//Limit error sto 1024 chars
	char buff[1024];
	va_list argumentPointer;

	//Start argument iteration so we can pass it to vsnprintf
	va_start(argumentPointer, errorFormat);

	//Let vsnprintf do our core work
	if( vsnprintf(buff, 1024, errorFormat, argumentPointer) <= 0 )
	{
		//Garauntee null termination at least
		buff[0] = 0;
	}

	//Technically only necessary on very specialized architectures, but here for compatibility
  	va_end(argumentPointer);
	lastError = buff;
}

/***************************************************************************
* signal handler. sets waitFlag to FALSE, to indicate above loop that	  *
* characters have been received.														 *
***************************************************************************/

void IntellistripSerial::signalHandlerIO(int status)
{
	printf_debug("received SIGIO signal.\n");
	waitFlag = FALSE;
}
